#include "ServerNet.h"
#include "MySignal.h"
#include "SocketHandle.h"
#include "../../common/config.h"

namespace XIAOO{
    void ServerNet::test(){
        std::cout<<"hello"<<std::endl;
    }

    void ServerNet::startUp(){
        int listenfd;
        listenfd = this->initSocket();

        //注册信号函数
        MySignal signalHandleObj;
        signalHandleObj.registMySignal(listenfd);

        this->doSelect(listenfd);
        
    }

    void ServerNet::doSelect(int listenfd){
        //客户端socket信息
        socklen_t clilen;
        
        sockaddr *cliaddr;
        //client表示可读的客户端fd，FD_SETSIZE默认1024
        int nready, client[FD_SETSIZE];
        //存放连接上来的客户端fd集
        fd_set rset, allset;
        int i, maxi, maxfd, connfd, sockfd;

        ssize_t n;
        
        maxfd = listenfd;
        maxi = -1;

        for(i = 0; i< FD_SETSIZE; i++){
            client[i] = -1;
        }
        FD_ZERO(&allset);
        FD_SET(listenfd, &allset);//参考Php的select版本，此行代码相当于$allset[] = $listenfd;

        //这个for应该 是无限循环，为了测试改为了有限数量
        // for(;;){
        for(int j = 0; j < 10000; j++){
            rset = allset;
            // select函数 int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
            // @param maxfdp linux下表示所有描述符中最大值+1，win下可以不用设置正确
            // @param readfds表示监听的所有fd_set结构的描述符集，
            // @param writefds和errorfds同上
            // @return readfds可读或者writefds可写 则返回>0，如果超过timeout返回=0，发生错误返回<0，如果timeout设置NULL表示不超时也就是阻塞
            // @return return>0的时候readfds参数会被重写为可读的fd集，举例：如果readfds=[1,2,3],select(maxfdp, readfds)，然后2,3可读，select()返回>0的同时，会把readfds重置为[2,3]
            nready = select(maxfd + 1, &rset, NULL, NULL, NULL);//阻塞

            // 判断被select函数重置的rset里面是否有listenfd,如果有表示有新的连接进来
            if(FD_ISSET(listenfd, &rset)){
                // new client coming
                clilen = sizeof(cliaddr);
                // 获取到新的连接的fd
                connfd = accept(listenfd, cliaddr, &clilen);
                for(i = 0; i < FD_SETSIZE; i++){
                    if(client[i] < 0){
                        // 把新的fd加入到client数组中
                        client[i] = connfd;
                        break;
                    }
                }
                if(i == FD_SETSIZE){
                    error("已达连接数上限", n);
                }

                // 把新的标识符加入allset，达到复用的目的
                FD_SET(connfd, &allset);

                // 这一步的目的是为了全局保持一个字符集的最大值，用于select()函数的第一个值
                if(connfd > maxfd){
                    maxfd = connfd;
                }

                if(i > maxi){
                    maxi = i;
                }

                // 如果select返回>1，则继续往下处理其他的读写fd，因为rset里只有一个listenfd所以新客户只会一次来一个
                // 注意++i和i++的写法，
                // 1、++i是先赋值i = i+1再取i的值，i++是取i的值之后再赋值i = i+1
                // 2、++i才能『左值引用』也就是放在赋值符号左边，i++不行
                if(--nready <= 0){
                    continue;
                }
            }

            //处理读写
            for (i = 0; i <= maxi; i++){
                if((sockfd = client[i]) < 0){
                    continue;
                }

                if(FD_ISSET(sockfd, &rset)){
                    char buffer[XIAOO::MAXBUFFER] = "";
                    //粘包处理方案1，设计协议：header，body, 服务器先读取header拿到一个size，再读取size，size就是body的长度
                    // char buffer1[10] = "";//读取header定义的长度，获取header json里面的bodySize参数，再读取bodySize长度
                    // read(sockfd, buffer1, 1024);
                    // char buffer2[10] = "";
                    // read(sockfd, buffer2, 10);
                    //粘包处理方案2，发送和接受都使用定长，比如1024byte

                    if((n = read(sockfd, buffer, MAXBUFFER)) == 0){
                        // @todo
                        std::cout << "【收到客户端退出的消息，sockfd=" << std::to_string(sockfd) << std::endl;
                        this->offlineClientHandle(sockfd, &allset, client, i);
                    }else{
                        // @todo以后可以创建个线程来处理业务
                        std::cout << "【收到客户端读写的消息，sockfd=" << std::to_string(sockfd) << std::endl;
                        this->clientHandle(sockfd, buffer, n);
                    }

                    if(--nready <= 0){
                        break;
                    }
                }
            }
            
        }

        
        std::cout << "ok"  << std::endl;    
    }

    // @todo客户端分成两个进程或者线程，一个主进程，子进程用来阻塞在read()显示接受的消息
    // @func 处理收到的请求
    // @步骤 1、处理read到的消息,解析成json对象，根据路由分发到各个route
    // @步骤 2、处理业务逻辑
    // @步骤 3、处理业务逻辑的返回，也就是write给客户端
    void ServerNet::clientHandle(int sockfd, char* buffer, int n){
        std::cout << "【服务器接收begin】" << std::endl;
        printf(buffer);
        printf("\r\nrecv size:%d", strlen(buffer));
        std::cout << "\n【服务器接收end】" << std::endl;
        //交给handle处理业务
        std::string writeBuffer;
        int writeBufferSize;

        SocketHandle handle;
        writeBuffer = handle.onReadHandle(sockfd, buffer);

        writeBufferSize = writeBuffer.length() + 1;
        if(writeBufferSize > XIAOO::MAXBUFFER){
            char* str;
            sprintf(str, "server发送内容超过buffer， MAXBUFFER=%d, msg.length()=%d", XIAOO::MAXBUFFER, writeBufferSize);
            error(str);
        }

        handle.onWriteHandle(sockfd, writeBuffer);

        // @todo 根据onReadHandle函数返回来判断，此次请求是单纯的 get请求 or 单聊 or 群聊(todo)
        // write(sockfd, (char*)writeBuffer.c_str(), writeBufferSize);
        // printf("send()=>%s\n", (char*)writeBuffer.c_str());
    }

    void ServerNet::offlineClientHandle(int sockfd, fd_set *allset, int client[], int i){
        //closed by client
        close(sockfd);
        FD_CLR(sockfd, allset);//类似unset($allset[$sockfd])
        client[i] = -1;

        SocketHandle handle;
        handle.onOfflineHandle(sockfd);

    }

    int ServerNet::initSocket(){
        int listenfd;
        int errorCode = 0;
        sockaddr_in serv_addr;

        XIAOO::Config config;
        Json::Value configData = config.readServerConfig();
        //test data
        int portno = configData["server"]["port"].asInt();
        const char* ip = configData["server"]["ip"].asString().c_str();
        //test data

        //创建socket句柄
        listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (listenfd < 0)
        {
            error("create socket失败");
        }

        //bind到端口
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr(ip);
        serv_addr.sin_port = htons(portno);

        errorCode = bind(listenfd, (sockaddr *)&serv_addr, sizeof(serv_addr));
        if (errorCode < 0)
        {
            error("bind socket失败", errorCode);
        }
        
        //listen //参数5表示listen的队列长度，单线程情况下最多只能有5个client连接到server
        listen(listenfd, 5);
        
        return listenfd;
    }


    // @todo学习线程的创建等
    void ServerNet::error(std::string errorMsg, int errorCode){
        std::cout<<errorMsg<<std::endl;
        printf("errorCode:%d\r\n", errorCode);
        exit(1);
    }

    ServerNet::ServerNet(){

    }

    ServerNet::~ServerNet(){
        
    }
}